package com.tongcao.cn.aspect;

import com.alibaba.common.convert.Convert;
import com.tongcao.cn.util.MapUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 @Author:tongcao
 @Date:2019/4/26 11:00 
 Description: 记录一次请求中,流经的所有方法的调用耗时及次数

 AOP基本概念
 理解概念至关重要。优雅设计的框架，通常包含一组相互紧密关联的概念。这些概念经过精心抽象和提炼而成。 AOP的基本概念主要有：

 Aspect：应用程序的某个模块化的关注点；通常是日志、权限、事务、打点、监控等。
 JointPoint：连接点，程序执行过程中明确的点，一般是方法的调用。
 Pointcut: 切点。指定施加于满足指定表达式的方法集合。Spring 默认使用 AspectJ pointcut 表达式。
 Advance: 通知。指定在方法执行的什么时机，不同的Advance对应不同的切面方法；有before,after,afterReturning,afterThrowing,around。
 TargetObject: 目标对象。通过Pointcut表达式指定的将被通知执行切面逻辑的实际对象。
 AOP proxy: 代理对象。由AOP框架创建的代理，用于回调实际对象的方法，并执行切面逻辑。Spring实现中，若目标对象实现了至少一个接口，则使用JDK动态代理，否则使用 CGLIB 代理。优先使用JDK动态代理。
 Weaving：织入。将切面类与应用对象关联起来。Spring使用运行时织入。
 通常 Pointcut 和 Advance 联合使用。即在方法上加上 @Advance(@Pointcut)
 */
@Component
@Aspect
public class MethodMeasureAspect {


    public static final Logger logger = LoggerFactory.getLogger(MethodMeasureAspect.class);

    private Map<String, Integer> methodCount = new ConcurrentHashMap();

    private Map<String, List<Integer>> methodCost = new ConcurrentHashMap<>();

    @Around("@annotation(com.tongcao.cn.annotation.MethodMeasureAnnotation)")
    public Object process(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = className + "_" + getMethodName(joinPoint);
        long startTime = System.currentTimeMillis();
        try {
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage());
        } finally {
            long costTime = System.currentTimeMillis() - startTime;
            logger.info("method={}, cost_time={}", methodName, costTime);
            methodCount.put(methodName, methodCount.getOrDefault(methodName, 0) + 1);
            List<Integer> costList = methodCost.getOrDefault(methodName, new ArrayList<>());
            costList.add(Convert.asInt(costTime));
            methodCost.put(methodName, costList);
        }
        return obj;
    }

    private String getMethodName(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSign = (MethodSignature) signature;
        return methodSign.getMethod().getName();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("MethodCount:\n");
        Map<String, Integer> sorted = MapUtil.sortByValue(methodCount);
        sorted.forEach(
                (method, count) -> {
                    sb.append("method=" + method + ", " + "count=" + count + '\n');
                }
        );
        sb.append('\n');
        sb.append("MethodCosts:\n");
        methodCost.forEach(
                (method, costList) -> {
                    IntSummaryStatistics stat = costList.stream().collect(Collectors.summarizingInt(x -> x));
                    String info = String.format("method=%s, sum=%d, avg=%d, max=%d, min=%d, count=%d", method,
                            (int) stat.getSum(), (int) stat.getAverage(), stat.getMax(), stat.getMin(), (int) stat.getCount());
                    sb.append(info + '\n');
                }
        );

        sb.append('\n');
        sb.append("DetailOfMethodCosts:\n");
        methodCost.forEach(
                (method, costList) -> {
                    String info = String.format("method=%s, cost=%s", method, costList);
                    sb.append(info + '\n');
                }
        );
        return sb.toString();
    }
}
